// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

////////////////////////////////////////////////////////////////////////////////
// This script will rarely need to be run.
//
// Usage:
//   node .\buildSemanticTokenScopes.js
//
// Result:
//   The current all relevant `package.json` files will be modified to contain
//   a bunch of custom semantic token scopes.
//
// For our custom grammar parser we need to register a bunch of semantic token
// types with VS Code and we essentially need one semantic token type per each
// type of TextMate grammar scope.  To get a reasonably complete list of tokens
// we simply read one of the default VS Code theme files and extract all of the
// listed scope names.
////////////////////////////////////////////////////////////////////////////////

const fs = require('fs');
const os = require('os');
const path = require('path');
const scopes = {};

// these are scope names that we've encountered in the wild that weren't in the
// default theme file, but we still want to colorize properly
const manualScopesToAdd = [
    'entity.name.function',
    'entity.name.variable.local',
    'entity.name.type',
    'entity.name.type.namespace',
    'punctuation.accessor',
    'punctuation.definition.variable',
    'punctuation.parenthesis',
    'punctuation.terminator.statement',
    'variable.other.object',
    'variable.other.readwrite',
    'variable.other.readwrite.global',
    // julia
    'meta.bracket',
    // python
    'meta.attribute',
    'meta.function',
    'meta.function-call.arguments',
    'meta.function-call.generic',
    'meta.indexed-name',
    'meta.item-access.arguments',
    'punctuation.definition.arguments.begin',
    'punctuation.definition.arguments.end',
    'punctuation.definition.list.begin',
    'punctuation.definition.list.end',
    'punctuation.definition.parameters.begin',
    'punctuation.definition.parameters.end',
    'punctuation.section.function.begin',
    'punctuation.section.function.end',
    'punctuation.separator.element',
    'punctuation.separator.colon',
    'punctuation.separator.period',
    'punctuation.separator.slice',
    'variable.parameter.function-call',
    // r
    'punctuation.section.brackets.single.begin',
    'punctuation.section.brackets.single.end',
    'punctuation.section.parens.begin',
    'punctuation.section.parens.end',
    'punctuation.separator.parameters',
    'support.function',
    'variable.other',
    'variable.function',
    'variable.parameter',
    // pwsh
    'meta.group.simple.subexpression',
    'punctuation.section.group.begin',
    'punctuation.section.group.end',
];

const semanticTokenPrefix = 'polyglot-notebook';
function addScope(scopeName) {
    if (scopeName.indexOf(' ') >= 0) {
        // not dealing with hierarchical scopes right now (separated with spaces)
        return;
    }

    // a scope like `comment.line` gets turned into `polyglot-notebook-comment-line`
    const specializedName = `${semanticTokenPrefix}-${scopeName.replace(/\./g, '-')}`;
    scopes[specializedName] = [scopeName];
    console.log(`added scope [${specializedName}] for [${scopeName}]`);
}

function processScopesFromThemePath(themePath) {
    const themeContents = fs.readFileSync(themePath, 'utf-8');

    // the theme file has trailing commas in its objects/arrays, so we have to do a terrible thing to read it
    const themeObject = eval('_ = ' + themeContents);
    for (const scopeColorBlock of themeObject.tokenColors) {
        if (typeof scopeColorBlock.scope === 'string') {
            addScope(scopeColorBlock.scope);
        }
        else if (Array.isArray(scopeColorBlock.scope)) {
            for (const scopeValue of scopeColorBlock.scope) {
                if (typeof scopeValue === 'string') {
                    addScope(scopeValue);
                }
            }
        }
    }
}

for (const manualScope of manualScopesToAdd) {
    addScope(manualScope);
}

const themeSubPath = 'Programs/Microsoft VS Code Insiders/resources/app/extensions/theme-defaults/themes/light_vs.json';
const themePath = path.join(process.env['LOCALAPPDATA'], ...themeSubPath.split('/'));
processScopesFromThemePath(themePath);

const projectJsonPaths = [
    path.join(__dirname, '..', 'package.json'), // insiders
    path.join(__dirname, '..', '..', 'polyglot-notebooks-vscode', 'package.json'), // stable
];

for (const projectJsonPath of projectJsonPaths) {
    console.log(`adding scopes to file ${projectJsonPath}`);
    const projectJsonContents = JSON.parse(fs.readFileSync(projectJsonPath));
    projectJsonContents.contributes.semanticTokenScopes = [
        {
            comment: 'the `scopes` object is generated by a script',
            scopes: scopes
        }
    ];

    let updatedProjectJsonContents = JSON.stringify(projectJsonContents, null, 2);
    if (os.platform() === 'win32') {
        updatedProjectJsonContents = updatedProjectJsonContents.replace(/\n/g, '\r\n');
    }

    fs.writeFileSync(projectJsonPath, updatedProjectJsonContents);
}
